/*
* Copyright (c) 2008, German Buela
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * The name of German Buela may not be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY GERMAN BUELA "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL GERMAN BUELA BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package org.gbuela.avant;

import java.util.Iterator;
import java.util.Properties;

import org.gbuela.avant.util.CommandClassTranslator;

/**
 * Provides context for Commands, to access request data, set the next action, etc.
 * @author gbuela
 */
public class CommandContext {
    public static final int FORWARD = 0;
    public static final int REDIRECT = 1;
    static final String REDIRECT_PREFIX = "redirect:";
    
    private Request request;
    private String nextAction = null;
    private int serveMethod = FORWARD;
    private Properties param;
    private CommandClassTranslator translator;
    
    /**
     * Creates a new instance of CommandContext
     */
    public CommandContext(Request request, CommandClassTranslator translator) {
    	param = new Properties();
        this.request = request;
        this.translator = translator;
    }
    
    /**
     * Initializes the context, reverting any next action setting to its default state.
     */
    public void init()
    {
        nextAction = null;
        serveMethod = FORWARD;
    }

    /**
     * Gets the request object, which has attributes of different scopes.
     */
    public Request getRequest() 
    {
        return request;
    }

    /**
     * Returns the next action as currently set.
     */
    public String getNextAction() {
        return nextAction;
    }

    /**
     * Sets the next action as a string following Avant's mapping conventions. Intended for
     * use by commands. The action is issued as a "forward" unless it is prefixed with "redirect:".
     * The overloaded method that takes a class instance is recommended for 
     * compiler checking and IDE refactoring.
     * @see CommandContext#setNextAction(Class cmdClass)
     * @see CommandContext#setNextActionRedirect(Class cmdClass)
     */
    public void setNextAction(String nextAction) {
        if(nextAction != null && nextAction.length() == 0)
            throw new IllegalArgumentException(Constants.NEXT_ACTION_EMPTY);

        if(nextAction.startsWith(this.REDIRECT_PREFIX) || !nextAction.endsWith(translator.getCommandSuffix()) || nextAction.startsWith("/"))
            this.nextAction = nextAction;
        else
            this.nextAction = "/" + nextAction;
    }
    /**
     * Sets the next action as a specific Command class, issued as a "forward". Intended for
     * use by commands. 
     * @see CommandContext#setNextAction(String nextAction)
     * @see CommandContext#setNextActionRedirect(Class cmdClass)
     */
    public void setNextAction(Class cmdClass)
    {
        this.setNextAction("/" + translator.toCommand(cmdClass));
    }
    /**
     * Sets the next action as a specific Command class, issued as a "redirect". Intended for
     * use by commands. 
     * @see CommandContext#setNextAction(String nextAction)
     * @see CommandContext#setNextAction(Class cmdClass)
     */
    public void setNextActionRedirect(Class cmdClass)
    {
        this.setNextAction(REDIRECT_PREFIX + translator.toCommand(cmdClass));
        this.serveMethod = REDIRECT;
    }
    /**
     * Returns the serve method as currently set. Possible values are FORWARED and REDIRECT.
     * The serve method is established through setNextAction or setServeMethod.
     * @see CommandContext#setServeMethod(int serveMethod)
     * @see CommandContext#setNextAction(String nextAction)
     * @see CommandContext#setNextAction(Class cmdClass)
     * @see CommandContext#setNextActionRedirect(Class cmdClass)
     */
    public int getServeMethod() {
        return serveMethod;
    }

    /**
     * Returns the URL for the next action including querystring parameters, but not the context path.
     * Intended for use by Avant's servlet to build the actual URL for the next action.
     */
    public String getNextURL()
    {
        if(nextAction == null)
            return null;
        
        String url;
        if(nextAction.startsWith(REDIRECT_PREFIX))
            url = nextAction.substring(REDIRECT_PREFIX.length());
        else
            url = nextAction;
        
        return url + buildQueryStringFromParams();
    }
    
    /**
     * Sets serve method for next command or view.
     * @param serveMethod FORWARD (default) | REDIRECT
     */
    public void setServeMethod(int serveMethod) {
        if(serveMethod != FORWARD && serveMethod != REDIRECT)
            throw new IllegalArgumentException(Constants.INVALID_SERVE_METHOD + " (" + serveMethod + ")");

        this.serveMethod = serveMethod;
    }

    /**
     * Gets the commands' root package as configured.
     * @return package name
     */
    public String getPackageName() {
        return translator.getPackageName();
    }

    /**
     * Adds a parameter to be used in next action's querystring.
     * @param name parameter to add
     * @param value the parameter's value
     */
    public void addParam(String name, String value)
    {
    	param.setProperty(name, value);
    }
    /**
     * Gets a parameter from next action's querystring (not to be confused with
     * the current request's parameters, for which getRequest().getParameter()
     * should be used).
     * @param name parameter name
     * @return parameter value
     * @see CommandContext#addParam(String name, String value)
     */
    public String getParam(String name)
    {
    	return param.getProperty(name);
    }
    /**
     * Clears the querystring parameters for the next action. Note that for any
     * command, this list of parameters is empty by default, until parameters are
     * set explicitly via addParam().
     * @see CommandContext#addParam(String name, String value)
     */
    public void clearParams()
    {
    	param.clear();
    }

    /**
     * Gets the object responsible for translating between command string and
     * command class.
     * @return the command/class translator
     */
    public CommandClassTranslator getTranslator() {
        return translator;
    }

    private String buildQueryStringFromParams()
    {
        if(param.size() == 0)
            return "";

        String qs = "?";
        Iterator ite = param.keySet().iterator();
        while(ite.hasNext())
        {
           String k = (String)ite.next();
           qs += k + "=" + param.getProperty(k);
        }
        return qs;
    }

    /**
     * Moves an attribute from the session scope to the request scope.
     * This method can be used to let the view make use of a session attribute (via the
     * request scope), when the value is not needed for further requests, so that the
     * session scope remains as "clean" as possible (the attribute is removed from the
     * session).
     * @param attName the attribute name
     */
    public void sessionToRequest(String attName)
    {
        Object o = getRequest().getSessionAttribute(attName);
        getRequest().setSessionAttribute(attName, null);
        getRequest().setAttribute(attName, o);
    }
}
